package net.sansi.v3correctserver.service.correct.linecorrect;

import net.sansi.v3correctserver.entity.IntLine;
import net.sansi.v3correctserver.entity.IntPoint;
import net.sansi.v3correctserver.entity.screen.CorrectArea;
import net.sansi.v3correctserver.entity.screen.Panel;
import net.sansi.v3correctserver.entity.screen.Rect;
import net.sansi.v3correctserver.entity.screen.ScanBoard;
import net.sansi.v3correctserver.service.correct.RectCorrectBase;
import net.sansi.v3correctserver.util.CorrectUtil;
import net.sansi.v3correctserver.util.Matrix;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Created by Fang
 * create time : 2024/11/25
 * IDE : IntelliJ IDEA
 */

public class RectLineCorrect extends RectCorrectBase {

    private final CorrectArea area;
    private final Params params;

    // 方向 [ 上，右，下，左]
    private final int[] DX = {0, 1, 0, -1};
    private final int[] DY = {-1, 0, 1, 0};

    // 采样距离
    private final int distance = 10;

    public RectLineCorrect(Params params) {
        this.params = params;
        area = params.area;
    }

    private final HashSet<RectLine> scanBoardLines = new HashSet<>();
    private final HashSet<RectLine> panelLines = new HashSet<>();

    /**
     * 开始矫正入口
     */
    public void toCorrect() {
        for (ScanBoard scanBoard : params.scanBoards) {
            scanBoardLines.addAll(Arrays.asList(RectLine.fromRect(scanBoard)));
        }
        for (Panel panel : params.panels) {
            panelLines.addAll(Arrays.asList(RectLine.fromRect(panel)));
        }
        for (RectLine line : scanBoardLines) {
            correctLine(line, true);
        }
        for (RectLine line : panelLines) {
            correctLine(line, false);
        }
    }

    // 已经矫正过了的线条
    private final HashSet<RectLine> correctedLines = new HashSet<>();

    /**
     * 矫正线条
     *
     * @param line        线条
     * @param isScanBoard 是否为扫描板
     */
    private void correctLine(RectLine line, boolean isScanBoard) {
        if (!checkLineNeedCorrect(line, isScanBoard)) return;
        double ratio = computeIntersectionRatio(line);
        RectLine neighborLine = getNeighborLine(line);
        if ((ratio > 1 && params.isCorrDark) || (ratio < 1) && params.isCorrLight) {
            double ratioA = Math.pow(ratio, params.attenuation);
            line.forEach(point -> changeResultBrightness(point, ratioA));
            neighborLine.forEach(point -> changeResultBrightness(point, ratioA));
        }
        correctedLines.addAll(List.of(line, neighborLine));
    }

    /**
     * 将某个led亮度调整到目标值
     *
     * @param point 点
     * @param ratio 比例系数
     */
    private void changeResultBrightness(IntPoint point, double ratio) {
        double[] rgb = params.result.get(point);
        if (rgb == null) return;
        rgb[0] *= ratio;
        rgb[1] *= ratio;
        rgb[2] *= ratio;
    }

    /**
     * 计算交集比例
     *
     * @param line 线条
     * @return 比例
     */
    private double computeIntersectionRatio(RectLine line) {
        Set<Long> left1 = new HashSet<>(), left2 = new HashSet<>();
        Set<Long> right1 = new HashSet<>(), right2 = new HashSet<>();
        Set<Long> ths1 = new HashSet<>(), ths2 = new HashSet<>();
        Matrix<Set<Long>> borders = params.ledBorders;
        line.forEach(point -> {
            IntPoint lp1 = getNeighborPoint(point, line.dir, -distance);
            IntPoint lp2 = getNeighborPoint(point, line.dir, -distance + 1);
            IntPoint tp1 = point;
            IntPoint tp2 = getNeighborPoint(point, line.dir, 1);
            IntPoint rp1 = getNeighborPoint(point, line.dir, distance);
            IntPoint rp2 = getNeighborPoint(point, line.dir, distance + 1);
            if (!isAllInAreaAndHavePixels(lp1, lp2, tp1, tp2, rp1, rp2)) return;
            pickPixel2Set(borders.get(lp1), left1);
            pickPixel2Set(borders.get(lp1), left2);
            pickPixel2Set(borders.get(tp1), ths1);
            pickPixel2Set(borders.get(tp2), ths2);
            pickPixel2Set(borders.get(rp1), right1);
            pickPixel2Set(borders.get(rp2), right2);
        });
        if (ths1.isEmpty()) return 0.0;
        int countL = computeIntersection(left1, left2);
        int countR = computeIntersection(right1, right2);
        int countT = computeIntersection(ths1, ths2);
        return countT * 1.0 / ((countL + countR) >> 1);
    }

    /**
     * 计算交集数量
     *
     * @param s1 集合1
     * @param s2 集合2
     * @return 交集数量
     */
    private int computeIntersection(Set<Long> s1, Set<Long> s2) {
        int count = 0;
        for (Long v : s1) {
            if (s2.contains(v)) count++;
        }
        return count;
    }

    /**
     * 是否全部符合要求
     * 在区域内， 同时存在像素信息
     *
     * @param points 点
     * @return 是否有效
     */
    private boolean isAllInAreaAndHavePixels(IntPoint... points) {
        for (IntPoint point : points) {
            if (!isInRect(point, area.getShowArea())) return false;
            Set<Long> pixels = params.ledBorders.get(point);
            if (pixels == null || pixels.isEmpty()) return false;
        }
        return true;
    }

    /**
     * 检查线条是否需要矫正
     *
     * @param line        线条
     * @param isScanBoard 是否为扫描板
     * @return 是否需要矫正
     */
    private boolean checkLineNeedCorrect(RectLine line, boolean isScanBoard) {
        if (correctedLines.contains(line)) return false;
        if (isScanBoard) {
            if (!params.isCorrScanBoard) return false;
            if (isOnBoundary(line, area.getShowArea())) return false;
        } else {
            if (!params.isCorrPanel) return false;
            if (!params.isCorrScanBoard && isOnScanBoard(line)) return false;
        }
        return true;
    }

    /**
     * 获取相邻点
     *
     * @param point  点
     * @param dir    方向
     * @param offset 偏移
     * @return 相邻点
     */
    private IntPoint getNeighborPoint(IntPoint point, int dir, int offset) {
        int x = point.getX() + DX[dir] * offset, y = point.getY() + DY[dir] * offset;
        return new IntPoint(x, y);
    }

    /**
     * 获取相邻线条
     *
     * @param line 线条
     * @return 相邻线条
     */
    private RectLine getNeighborLine(RectLine line) {
        int dir = line.dir;
        return new RectLine(
                new IntLine(
                        new IntPoint(line.p1.getX() + DX[dir], line.p1.getY() + DY[dir]),
                        new IntPoint(line.p2.getX() + DX[dir], line.p2.getY() + DY[dir])
                ),
                (dir + 2) % 4
        );
    }

    /**
     * 将 led 像素填充到集合中
     *
     * @param borders 边界
     * @param target  目标集合
     */
    private void pickPixel2Set(Set<Long> borders, Set<Long> target) {
        int[] fence = {Integer.MAX_VALUE, 0, 0, Integer.MAX_VALUE};
        borders.forEach(value -> {
            int[] p = CorrectUtil.long2Xy(value);
            fence[0] = Math.min(fence[0], p[1]);
            fence[1] = Math.max(fence[1], p[0]);
            fence[2] = Math.max(fence[2], p[1]);
            fence[3] = Math.min(fence[3], p[0]);
        });
        int top = fence[0], right = fence[1], bottom = fence[2], left = fence[3];
        for (int x = left; x <= right; x++) {
            int t = top, b = bottom;
            while (borders.contains(CorrectUtil.xy2Long(x, t))) t++;
            while (borders.contains(CorrectUtil.xy2Long(x, b))) b--;
            while (t <= b) {
                target.add(CorrectUtil.xy2Long(x, t));
                target.add(CorrectUtil.xy2Long(x, b));
                t++;
                b--;
            }
        }
    }

    /**
     * 是否在扫描板上
     *
     * @param line 线条
     * @return boolean
     */
    private boolean isOnScanBoard(IntLine line) {
        for (IntLine l : scanBoardLines) {
            if (l == line) return true;
        }
        return false;
    }

    /**
     * 亮暗线矫正
     */
    public static class Params {
        CorrectArea area;
        List<Panel> panels;
        List<ScanBoard> scanBoards;
        Matrix<double[]> result;
        Matrix<Set<Long>> ledBorders;
        boolean isCorrPanel = true;
        boolean isCorrScanBoard = true;
        boolean isCorrLight = true;
        boolean isCorrDark = true;
        double attenuation = 2.2;
    }

    /**
     * 仅用于亮暗线矫正的线条实体
     */
    private static class RectLine extends IntLine {

        public RectLine(IntLine line, int dir) {
            super(line.p1, line.p2);
            this.dir = dir;
        }

        /**
         * 通过区域获取线条数组
         * 上 右 下 左 线条
         *
         * @param rect 区域
         * @return 线条数组
         */
        public static RectLine[] fromRect(Rect rect) {
            return new RectLine[]{
                    new RectLine(rect.getLine(0), 0),
                    new RectLine(rect.getLine(1), 1),
                    new RectLine(rect.getLine(2), 2),
                    new RectLine(rect.getLine(3), 3)
            };
        }

        // 方向 [ 上，右，下，左]
        public int dir;

        public interface ForeachCallback {
            void next(IntPoint point);
        }

        /**
         * 迭代器遍历
         *
         * @param iterator 迭代器
         */
        public void forEach(ForeachCallback iterator) {
            int x1 = Math.min(p1.getX(), p2.getX()), x2 = Math.max(p1.getX(), p2.getX());
            int y1 = Math.min(p1.getY(), p2.getY()), y2 = Math.max(p1.getY(), p2.getY());
            if (x1 == x2) {
                for (int y = y1; y <= y2; y++) {
                    iterator.next(new IntPoint(x1, y));
                }
            } else {
                for (int x = x1; x <= x2; x++) {
                    iterator.next(new IntPoint(x, y1));
                }
            }
        }

        @Override
        public boolean equals(Object obj) {
            return super.equals(obj);
        }

        @Override
        public int hashCode() {
            return super.hashCode();
        }
    }
}
